﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Diagnostics;
using Flatland.Utilities;

/**
* 
* Copyright (c) 2012 Anders Høst Kjærgaard | ahkj@itu.dk
* 
* 
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
* associated documentation files (the "Software"), to deal in the Software without restriction, 
* including without limitation the rights to use, copy, modify, merge, publish, distribute, 
* sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial 
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 
* NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

namespace Flatland.GeneticProgramming.CoreTypes {

    /// <summary>
    /// Primitive member of the primitive set 
    /// </summary>
    [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field)]
    public class Primitive : Attribute {
        /// <summary>
        /// Factor of of how many times the PrimitiveSet will occur in a terminal or function set. 
        /// </summary>
        public int Factor { get; set; }

        /// <summary>
        /// Constructor for PrimitiveSet with a user defined factor. Default is 1
        /// </summary>
        /// <param name="factor"></param>
        public Primitive(int factor) {
            Factor = factor;
        }

        /// <summary>
        /// Default construct. Factor 1
        /// </summary>
        public Primitive() {
            Factor = 1;
        }
    }

    /// <summary>
    /// PrimitiveSet parses the PrimitiveSet from a type into functional and terminal sets of the genetic program.
    /// </summary>
    [Serializable]
    public class PrimitiveSet {

        /// <summary>
        /// The terminal set consisting of the Primitive attributed class members.
        /// </summary>
        public List<Terminal> TerminalSet { get; set; }

        /// <summary>
        /// The function set consisting of the PrimitiveSet attributed class members.
        /// </summary>
        public List<Function> FunctionSet { get; set; }

        /// <summary>
        /// Closure type of the GP run. This is per convention the realized class parameter of GeneExpression, or bool
        /// for the GeneCommand
        /// </summary>
        public Type ClosureType { get; set; }

        /// <summary>
        /// Private. The type that the PrimitiveSet is parsed from.
        /// </summary>
        private GenoType _gene;

        /// <summary>
        /// True if the GenoType uses commands
        /// </summary>
        public bool IsCommand { get; set; }

        /// <summary>
        /// By convention; our basic flags for building our Tree are non-dereived instance members only. 
        /// This restriction limits the base class of setting attributes in any but itself and avoid any static methods.
        /// </summary>
        public static BindingFlags BasicFlags = BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Instance;

        /// <summary>
        /// Parse a type by GP conventions.
        /// Class declaring the PrimitiveSet attributes:
        ///     - is a direct subclass of either GeneCommand or GeneExpression.
        ///     - is declaring a public parameterless constructor and should function as intended when initialised by this.
        ///     - obeying the above, it may contain any paraterized constructors to be used for the created genetic code
        ///     - may itself be a subclass of the GenoType instance sent to Parse
        ///     - will be initialised during parse.
        /// </summary>
        /// <param name="genoType">GenoType to be parsed to function and terminal sets</param>
        public void Parse(GenoType genoType) {
            if (genoType == null) throw new ArgumentNullException("genoType");
            if (UserPreferences.VERBOSE)
                Console.WriteLine("PrimitiveSet parsing type: {0}\nWith closure: {1}", genoType, ClosureType);
            _gene = genoType;
            ParseClosureType();
            FunctionSet = ParseFunctions();
            if (IsCommand) {
                AddCommandFunctions();
            }
            TerminalSet = ParseTerminalFunctions();
            TerminalSet.AddRange(ParseTerminalFields());
            if (IsCommand && BooleanSets().TerminalSet.Count == 0)
                AddBooleanTerminals(TerminalSet);
        }

        /// <summary>
        /// Finds the closure type. Bool if not IExpression
        /// </summary>
        private void ParseClosureType() {
            if (_gene.GetType().BaseType != typeof(GenoType))
                throw new TypeInitializationException("Implementer must be direct subclass of GenoType", new Exception());
            foreach (var @interface in _gene.GetType().GetInterfaces()) {
                if (@interface.Name.Equals("IExpression`1")) {
                    ClosureType = @interface.GetGenericArguments().First();
                    return;
                }
            }
            ClosureType = typeof(bool);
            IsCommand = true;
        }

        /// <summary>
        /// Get the members with the PrimitiveSet attribute and functional signature, i.e. parameterized methods with a return value.
        /// </summary>
        /// <returns></returns>
        private List<Function> ParseFunctions() {
            MethodInfo[] mis = _gene.GetType().GetMethods(BasicFlags).Where(mi => {
                var firstOrDefault = Attribute.GetCustomAttributes(mi).FirstOrDefault();
                return firstOrDefault != null && firstOrDefault.GetType() == typeof(Primitive) && mi.GetParameters().Any();
            }).ToArray();
            List<Function> list = new List<Function>(mis.Length);
            foreach (MethodInfo mi in mis) {
                if (FunctionMethodClosure(mi)) {
                    var dna = (Primitive)Attribute.GetCustomAttribute(mi, typeof(Primitive));
                    for (int i = 0; i < dna.Factor; i++)
                        list.Add(new Function(mi));
                }
                if (UserPreferences.VERBOSE)
                    PrintMethodInfoForPrimitive(mi, typeof(Function).ToString());
            }
            return list;
        }


        /// <summary>
        /// Returns true if no method parameter or return type are of closure type.
        /// </summary>
        /// <param name="mi">MethodInfo to check for closure</param>
        /// <returns></returns>
        private bool FunctionMethodClosure(MethodInfo mi) {
            if (mi.GetParameters().Any(parameterInfo => parameterInfo.ParameterType != ClosureType)) {
                return false;
            }
            return mi.ReturnType == ClosureType;
        }


        /// <summary>
        /// Adds command Functions to the function set if GeneCommand
        /// </summary>
        private void AddCommandFunctions() {
            FunctionSet.Add(new Function(SetType.IF_THEN_ELSE));
            FunctionSet.Add(new Function(SetType.SEQUENCE));
        }

        /// <summary>
        /// Adds command terminal a terminal set
        /// Currently constants true and false
        /// </summary>
        private void AddBooleanTerminals(List<Terminal> set) {
            set.Add(new Terminal(true));
            set.Add(new Terminal(false));
        }


        /// <summary>
        /// Get the method members with the PrimitiveSet attribute and adds them to the Terminal set
        /// Terminal functions are defined by being parameterless methods with return closure type, or possibly void if GeneCommand is used.
        /// </summary>
        /// <returns></returns>
        private List<Terminal> ParseTerminalFunctions() {
            MethodInfo[] mis = _gene.GetType().GetMethods(BasicFlags).Where(mi => {
                var firstOrDefault = Attribute.GetCustomAttributes(mi).FirstOrDefault();
                return firstOrDefault != null && firstOrDefault.GetType() == typeof(Primitive) && (0 == mi.GetParameters().Length && TerminalMethodClosure(mi));
            }).ToArray();
            List<Terminal> list = new List<Terminal>(mis.Length);
            foreach (MethodInfo mi in mis) {
                var dna = (Primitive)Attribute.GetCustomAttribute(mi, typeof(Primitive));
                for (int i = 0; i < dna.Factor; i++)
                    list.Add(new Terminal(mi));
                if (UserPreferences.VERBOSE)
                    PrintMethodInfoForPrimitive(mi, typeof(Terminal).ToString());
            }
            return list;
        }


        /// <summary>
        /// Returns true if return type is T, or void and parsing GeneCommand
        /// </summary>
        /// <param name="mi"></param>
        /// <returns></returns>
        private bool TerminalMethodClosure(MethodInfo mi) {
            return mi.ReturnType == ClosureType || (mi.ReturnType == typeof(void) && IsCommand);
        }


        /// <summary>
        /// Get the field members with the PrimitiveSet attribute and adds them to the Terminal set.
        /// Field values are also checked for closure.
        /// </summary>y
        /// <returns></returns>
        private List<Terminal> ParseTerminalFields() {
            FieldInfo[] fis = _gene.GetType().GetFields();
            var list = new List<Terminal>(fis.Count());
            foreach (FieldInfo fi in fis.Where(fi => {
                var firstOrDefault = Attribute.GetCustomAttributes(fi).FirstOrDefault();
                return firstOrDefault != null && firstOrDefault.GetType() == typeof(Primitive);
            })) {

                //Parameter declaration, by convention arrays. 
                if (fi.GetValue(_gene).GetType().IsArray) {
                    var prim = (Primitive)Attribute.GetCustomAttribute(fi, typeof(Primitive));
                    for (int i = 0; i < prim.Factor; i++)
                    {
                        var enumerator = ((IEnumerable)fi.GetValue(_gene)).GetEnumerator();
                        int j = 0;
                        while (enumerator.MoveNext()) {
                            if (enumerator.Current.GetType() != ClosureType) throw new Exception("Found type " + enumerator.Current.GetType() + " in PrimitiveSet list declaration, expected closure type " + ClosureType);
                            list.Add(new Terminal(fi, j));
                            ++j;
                        }   
                    }
                }

                //Variable declaration
                else if (fi.FieldType == ClosureType) {
                    var dna = (Primitive)Attribute.GetCustomAttribute(fi, typeof(Primitive));
                    for (int i = 0; i < dna.Factor; i++)
                        list.Add(new Terminal(fi, fi.Name));
                }
                if (UserPreferences.VERBOSE)
                    PrintFieldInfo(fi);
            }
            return list;
        }


        /// <summary>
        /// Factory method for getting a PrimitiveSet where the Function and Terminal set is restricted to boolean resolvents.
        /// </summary>
        /// <returns></returns>
        public PrimitiveSet BooleanSets() {
            PrimitiveSet cp = new PrimitiveSet();
            cp.FunctionSet = FunctionSet.Where(
                    f => f.SetType == SetType.FUNCTION && f.MethodInfo.ReturnType == typeof(bool)).ToList();
            cp.TerminalSet = TerminalSet.Where(
                     t => t.SetType == SetType.ZERO_ARITY_FUNCTION && t.MethodInfo.ReturnType == typeof(bool) ||
                           t.SetType == SetType.ARRAY_INDEX && t.FieldInfo.FieldType.GetElementType() == typeof(bool) ||
                           t.SetType == SetType.VARIABLE && t.FieldInfo.FieldType == typeof(bool) ||
                           t.SetType == SetType.CONSTANT && t.Constant is bool).ToList();
            return cp;
        }

        /// <summary>
        /// Factory method for getting a PrimitiveSet where the Function and Terminal set is restricted to commands, i.e. void Methods, and conditionals
        /// </summary>
        /// <returns></returns>
        public PrimitiveSet CommandSets() {
            PrimitiveSet cp = new PrimitiveSet();
            cp.FunctionSet = FunctionSet.Where(
                    f => f.SetType == SetType.IF_THEN_ELSE || f.SetType == SetType.SEQUENCE).ToList(); ;
            cp.TerminalSet = TerminalSet.Where(
                     t => t.SetType == SetType.ZERO_ARITY_FUNCTION && t.MethodInfo.ReturnType == typeof(void)).ToList(); ;
            return cp;
        }

        /// <summary>
        /// Factory method for getting a PrimitiveSet where the Function and Terminal are not void
        /// </summary>
        /// <returns></returns>
        public PrimitiveSet TypedSet() {
            PrimitiveSet cp = new PrimitiveSet();
            cp.FunctionSet = FunctionSet.Where(
                    f => f.SetType == SetType.FUNCTION && f.MethodInfo.ReturnType == ClosureType).ToList();
            cp.TerminalSet = TerminalSet.Where(
                     t => t.SetType == SetType.ZERO_ARITY_FUNCTION && t.MethodInfo.ReturnType == ClosureType ||
                           t.SetType == SetType.ARRAY_INDEX && t.FieldInfo.FieldType.GetElementType() == ClosureType ||
                           t.SetType == SetType.VARIABLE && t.FieldInfo.FieldType == ClosureType).ToList();
            if(IsCommand)
                AddBooleanTerminals(cp.TerminalSet);
            return cp;
        }

        /*
         * A few methods console feedback
         */
        private void PrintMethodInfoForPrimitive(MethodInfo mi, String primitiveType) {
            Debug.Assert(mi != null, "Type parameter is null", "Argument was null");
            Debug.Assert(mi.GetType() != typeof(MethodInfo), "Wrong parameter type", "PrintMethodDetails type should be {0} but found {1}" + typeof(MethodInfo) + " " + mi.GetType());
            Console.WriteLine("Found method: {0}\nPrimitive: {1}\nParams: {2}\n", mi.Name, primitiveType,
                mi.GetParameters().Select(p => p.Name).DefaultIfEmpty("NO PARAMETERS").Aggregate((s1, s2) => s1 + ", " + s2));
        }

        private void PrintFieldInfo(FieldInfo fi) {
            Debug.Assert(fi != null, "Type parameter is null", "Argument was null");
            Debug.Assert(fi.GetType() != typeof(FieldInfo), "Wrong parameter type", "PrintFieldInfo type should be {0} but found {1}" + typeof(FieldInfo) + " " + fi.GetType());
            Console.WriteLine("Found Field: {0}\nType: {1}\n", fi.Name, fi.GetType());
        }
    }
}